package com.fengye.security.jwt.process;

import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.http.MediaType;
import org.springframework.security.authentication.AuthenticationServiceException;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.authentication.WebAuthenticationDetails;
import org.springframework.security.web.authentication.preauth.PreAuthenticatedCredentialsNotFoundException;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.util.StringUtils;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.InputStream;

/**
 * 用户登录验证拦截器 执行顺序在UsernamePasswordAuthenticationFilter 拦截器后
 *
 * @author sky
 * @since 2021/4/13 9:45 下午
 */
public class JwtLoginFilter extends UsernamePasswordAuthenticationFilter {

    private static final AntPathRequestMatcher DEFAULT_ANT_PATH_REQUEST_MATCHER = new AntPathRequestMatcher("/jwt/login",
            "POST");

    public JwtLoginFilter() {
        // super.setFilterProcessesUrl("/jwt/login");
        super.setRequiresAuthenticationRequestMatcher(DEFAULT_ANT_PATH_REQUEST_MATCHER);
    }


    /**
     * 登录逻辑,在此获取用户的登录信息,spring security进行保存,然后跳转到 {@link JwtAuthenticationProvider} 进行判断
     *
     * @param request {@link HttpServletRequest}
     * @param response {@link HttpServletResponse}
     * @return {@link Authentication}
     * @throws AuthenticationException {@link AuthenticationException} 用户认证失败异常
     */
    @Override
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
            throws AuthenticationException {


        if (!request.getMethod().equals("POST")) { // 方法不支持
            throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());
        }

        // if (!requiresAuthentication(request, response)) {
        //     chain.doFilter(request, response);
        //     return;
        // }

        // 获取用户名和密码
        JwtAuthenticationBean authenticationBean = this.getAuthenticationBeanFromRequest(request);

        // 用户名获取失败,抛出登录失败
        // {@link JwtLoginFailureHandler} 来处理输出
        if (!StringUtils.hasLength(authenticationBean.getUsername().trim())) {
            throw new PreAuthenticatedCredentialsNotFoundException("用户名不能为空");
        }

        // 创建未认证的凭证(etAuthenticated(false)),注意此时凭证中的主体principal为用户名
        JwtAuthenticationToken jwtAuthenticationToken = new JwtAuthenticationToken(authenticationBean.getUsername(),
                authenticationBean.getPassword());

        // 将认证详情(ip,sessionId)写到凭证
        jwtAuthenticationToken.setDetails(new WebAuthenticationDetails(request));

        // AuthenticationManager获取受支持的AuthenticationProvider(这里也就是JwtAuthenticationProvider),
        // 生成已认证的凭证,此时凭证中的主体为userDetails --- 这里会委托给AuthenticationProvider实现类来验证
        // 即 跳转到 JwtAuthenticationProvider.authenticate 方法中认证
        return this.getAuthenticationManager().authenticate(jwtAuthenticationToken);
    }

    /**
     * request的传参格式为 application/json 获取json的用户登录信息
     * @param request {@link HttpServletRequest} 携带有登录信息,以 {@code application/json}格式, 或者
     * {@code application/x-www-form-urlencoded}都支持
     * @return {@link JwtAuthenticationBean}
     */
    @SuppressWarnings("deprecation")
    @Deprecated
    private JwtAuthenticationBean getPreAuthenticatedPrincipalFromRequest(HttpServletRequest request) {

        // application/x-www-form-urlencoded 提交的请求,获取用户名和密码
        String username = super.obtainUsername(request);
        String password = super.obtainPassword(request);
        JwtAuthenticationBean authenticationBean = new JwtAuthenticationBean();
        if (StringUtils.isEmpty(username)) { // RequestParam获取用户名失败,则从头部的Authorization中获取
            try {
                // 仅支持 application/json
                if (request.getContentType().equals(MediaType.APPLICATION_JSON_UTF8_VALUE)
                        || request.getContentType().equals(MediaType.APPLICATION_JSON_VALUE)) {

                    try (InputStream is = request.getInputStream()) {
                        ObjectMapper mapper = new ObjectMapper();
                        authenticationBean = mapper.readValue(is, JwtAuthenticationBean.class);
                        logger.debug("成功获取登录信息: " + authenticationBean);
                        username = authenticationBean.getUsername();
                        password = authenticationBean.getPassword();
                    } catch (IOException e) {
                        logger.warn("APPLICATION_JSON方式读取登录信息失败");
                        return new JwtAuthenticationBean();
                    }
                }
            } catch (Exception e){
            	logger.warn(e.getMessage());
				throw new BadCredentialsException("用户名不能为空");
			}
        }
        authenticationBean.setUsername(username);
        authenticationBean.setPassword(password);

        return authenticationBean;
    }
    private JwtAuthenticationBean getAuthenticationBeanFromRequest(HttpServletRequest request) {
        if (StringUtils.hasText(request.getContentType()) && request.getContentType().equals(MediaType.APPLICATION_JSON_VALUE)) {

            try (InputStream is = request.getInputStream()) {
                ObjectMapper mapper = new ObjectMapper();
                JwtAuthenticationBean authenticationBean  = mapper.readValue(is, JwtAuthenticationBean.class);
                authenticationBean.setUsername(authenticationBean.getUsername().trim());
                authenticationBean.setPassword(authenticationBean.getPassword().trim());
                logger.debug("成功获取登录信息: " + authenticationBean);
                return authenticationBean;
            } catch (IOException e) {
                logger.warn("APPLICATION_JSON方式读取登录信息失败");
                return new JwtAuthenticationBean();
            }
        }
        throw new AuthenticationServiceException("Content-Type not supported: " + request.getContentType());

    }
}